/*
 * NyuContext.cpp
 *
 *  Created on: 2010/05/21
 *      Author: mitsu
 */

#include <iostream>
#include <cstring>
#include "NyuContext.hpp"
#include "NyuTree.hpp"
#include "NyuStrPool.hpp"
#include "NyuSymtbl.hpp"
#include "NyuString.hpp"
#include "NyuGC.hpp"
#include "NyuRange.hpp"
#include "NyuArray.hpp"

namespace {

int init_count = 0;

}

NyuContext::NyuContext() :
		current_scope_(new NyuScope()), scope_cache_idx_(0),
		is_scope_updated_(false), symbols_(32) {
	if (!init_count++) {
        NyuStrPool::initialize();
        NyuSymtbl::initialize();
	}

    NyuGC::addContext(this);

    NyuObject* root = new NyuObject();
    put_symref(NyuSymtbl::getIndex(&NyuStaticString::OBJECT),
               NYU_OBJECT_TO_VALUE(root));

    NyuObject* string = new NyuObject(root);
    string->putSlot(&NyuStaticString::FROM_CHAR_CODE,
                    NYU_FUNC_TO_VALUE(NyuString::fromCharCodeFunc), 0);
    put_symref(NyuSymtbl::getIndex(&NyuStaticString::STRING),
               NYU_OBJECT_TO_VALUE(string));

    NyuObject* range = new NyuObject(root);
    range->putSlot(&NyuStaticString::EACH,
                   NYU_FUNC_TO_VALUE(NyuRange::each_func), 0);
    put_symref(NyuSymtbl::getIndex(&NyuStaticString::RANGE),
               NYU_OBJECT_TO_VALUE(range));

    NyuObject* array = new NyuObject(root);
    array->putSlot(&NyuStaticString::EACH,
                   NYU_FUNC_TO_VALUE(NyuArray::each_func), 0);
    array->putSlot(&NyuStaticString::LENGTH,
                   NYU_FUNC_TO_VALUE(NyuArray::length_func), 0);
    array->putSlot(&NyuStaticString::PUSH,
                   NYU_FUNC_TO_VALUE(NyuArray::push_func), 0);
    array->putSlot(&NyuStaticString::POP,
                   NYU_FUNC_TO_VALUE(NyuArray::pop_func), 0);
    put_symref(NyuSymtbl::getIndex(&NyuStaticString::ARRAY),
               NYU_OBJECT_TO_VALUE(array));
}

NyuContext::~NyuContext() {
	if (!--init_count) {
		NyuStrPool::finalize();
        NyuSymtbl::finalize();
	}

    NyuGC::removeContext(this);
}

void NyuContext::set_func(const char* name, NyuFunc func) {
    NyuString* name_str = NyuStrPool::get(::strdup(name));
    unsigned int symidx = NyuSymtbl::getIndex(name_str);
    put_symref(symidx, NYU_FUNC_TO_VALUE(func));
}

NyuValue NyuContext::callValue(NyuValue v1, bool is_tail) {
    NyuValue ret = 0;

    if (is_tail) {
        update_scope(v1, 0);
        return 0;
    }

    push_scope(v1, 0);

call_val:

    switch (NYU_VALUE_TAG(v1)) {
    case NYU_VALUE_TAG_INT:
        std::cerr << "int is not callable!" << std::endl;
        break;

    case NYU_VALUE_TAG_NATIVE:
        ret = NYU_VALUE_UNTAG_NATIVE(v1)(this);
        break;

    case NYU_VALUE_TAG_OBJECT:
        v1 = NYU_VALUE_UNTAG_OBJECT(v1)->getCall(this);
        current_scope_->argv_[0] = current_target_;
        goto call_val;

    case NYU_VALUE_TAG_BLOCK:
        {
            NyuTreeList* stmts = NYU_VALUE_UNTAG_BLOCK(v1);
            if (stmts)
                ret = stmts->eval(this);
        }
        if (is_scope_updated()) {
            v1 = current_block();
            set_is_scope_updated(false);
            goto call_val;
        }
        break;
    }

    pop_scope();

    return ret;
}

NyuValue NyuContext::callValue(NyuValue v1, NyuValue v2, bool is_tail) {
    NyuValue ret = 0;

    if (is_tail) {
        update_scope(v1, 0);
        current_scope_->argc_ = 2;
        current_scope_->argv_[1] = v2;
        return 0;
    }

    push_scope(v1, 0);
    current_scope_->argc_ = 2;
    current_scope_->argv_[1] = v2;

call_val:

    switch (NYU_VALUE_TAG(v1)) {
    case NYU_VALUE_TAG_INT:
        std::cerr << "int is not callable!" << std::endl;
        break;

    case NYU_VALUE_TAG_NATIVE:
        ret = NYU_VALUE_UNTAG_NATIVE(v1)(this);
        break;

    case NYU_VALUE_TAG_OBJECT:
        v1 = NYU_VALUE_UNTAG_OBJECT(v1)->getCall(this);
        current_scope_->argv_[0] = current_target_;
        goto call_val;

    case NYU_VALUE_TAG_BLOCK:
        {
            NyuTreeList* stmts = NYU_VALUE_UNTAG_BLOCK(v1);
            if (stmts)
                ret = stmts->eval(this);
        }
        if (is_scope_updated()) {
            v1 = current_block();
            set_is_scope_updated(false);
            goto call_val;
        }
        break;
    }

    pop_scope();

    return ret;
}

NyuScope* NyuContext::new_scope(NyuScope* parent, NyuValue block) {
	return new NyuScope(parent, block);
}

void NyuContext::delete_scope(NyuScope* scope) {
	delete scope;
}
